#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
from __future__ import print_function
import os
import json
import time
import datetime
import psycopg2
import threading

# from pydash import at

from communication_client.communication_client import Client

####################
# global var
####################
###
client_pd = Client('plan_dispatch')
client_og = Client('object_generator')
###
paras_file = './conf/server.conf'
###
log_file = None
notices = []  # max length: 30
###
db = None
###
groups = []                 # ['XL001', 'XL002', 'XL003']
units = {}                  # {'XL001': ['001','002'], ...}
devices = []                # [('XL001','001'), ...]
device_name = {}            # {('XL001','001'): 'GWAC001', ...}
objs_of_device = {}         # {('XL001','001'): [1,2,3], ...}
obj_pri_of_device = {}      # {('XL001','001'): [20], ...}
objs_in_check = []          # [1,2, ...] before send
###
oberving_lock = threading.Lock()
checking_lock = threading.Lock()
###

####################
# tool function
####################


def load_params(json_file):
    with open(json_file, 'r') as read_file:
        pd_params = json.load(read_file)
    return pd_params


def con_db():
    pd_params = load_params(paras_file)
    try:
        db = psycopg2.connect(**pd_params['database'])
    except psycopg2.Error as e:
        print(e)
        return False
    else:
        return db


def sql_act(sql, n=1):
    db = con_db()
    if db:
        try:
            cur = db.cursor()
            cur.execute(sql)
            if n == 0:
                db.commit()
                cur.close()
                db.close()
                return
            else:
                rows = cur.fetchall()
                cur.close()
                db.close()
                return rows
        except psycopg2.Error as e:
            print(e)
            return False
    else:
        print("\n\033[0;31mWARNING:\033[0m Connection to the db is Error.")
        return 0


def pg_act(table, action, args=[]):
    if args:
        if action == 'delete':
            cond_keys = args[0].keys()
            conds = []
            for key in cond_keys:
                conds.append("='".join([key, args[0][key]]))
            cond = "' AND ".join(conds)
            sql = "DELETE FROM " + table + " WHERE " + cond + "'"
            sql_act(sql, 0)
        if action == "insert":
            # args = [{'obj_id':'1',}]
            rows = args[0].keys()
            vals = []
            for row in rows:
                vals.append(args[0][row])
            sql = "INSERT INTO " + table + \
                " (" + ", ".join(rows) + ") VALUES ('" + "', '".join(vals) + "')"
            sql_act(sql, 0)
        if action == "update":
            # args = [{'obj_name':'x','obj_comp_time':'2019-01-01 00:00:00'},{'obj_id':'1','obs_stag':'sent'}]
            rows = args[0].keys()
            targs = []
            for row in rows:
                targs.append("='".join([row, args[0][row]]))
            targ = "' , ".join(targs)
            cond_keys = args[1].keys()
            conds = []
            for key in cond_keys:
                conds.append("='".join([key, args[1][key]]))
            cond = "' AND ".join(conds)
            sql = "UPDATE " + table + " SET " + targ + "' WHERE " + cond + "'"
            sql_act(sql, 0)
        if action == "select":
            # args = [['1','2'],{'obj_name':'x','obj_comp_time':'2019-01-01 00:00:00'}]
            rows = ','.join(args[0])
            if args[1]:
                cond_keys = args[1].keys()
                conds = []
                for key in cond_keys:
                    conds.append("='".join([key, args[1][key]]))
                cond = "' AND ".join(conds)
                cond += "'"
            else:
                cond = ''
            if len(args) > 2:
                cond_more = args[2]
            else:
                cond_more = ''
            sql = "SELECT " + rows + " FROM " + table + " WHERE " + cond + cond_more
            # print sql
            res = sql_act(sql)
            return res


# actype: 'add', 'delete'
def observing_list_maintenance(actype, device, obj_id, obj_pri):
    global objs_of_device, obj_pri_of_device
    with oberving_lock:
        if actype == 'add':
            if obj_id not in objs_of_device[device]:
                objs_of_device[device].append(obj_id)
            if obj_id not in obj_pri_of_device[device]:
                obj_pri_of_device[device].append(int(obj_pri))
        if actype == 'delete':
            if obj_id in objs_of_device[device]:
                objs_of_device[device].remove(obj_id)
            if obj_id in obj_pri_of_device[device]:
                obj_pri_of_device[device].remove(int(obj_pri))


def checking_list_maintenance(actype, obj_id):  # actype: 'add', 'delete'
    global objs_in_check
    res_code = 0
    with checking_lock:
        if actype == 'add':
            if obj_id not in objs_in_check:
                objs_in_check.append(obj_id)
                res_code = 1
        if actype == 'delete':
            if obj_id in objs_in_check:
                objs_in_check.remove(obj_id)
                res_code = 1
    return res_code


def Ra_to_h(ra):
    if ':' in ra:
        RaList = list(map(float, ra.split(':')))
        return str(RaList[0] + RaList[1]/60 + RaList[2]/3600)
    elif abs(float(ra)) <= 360:
        a = float(ra)/15
        return str(a)
    else:
        exit('\n\033[0;31mWARNING:\033[0m Wrong RA.')


def get_msg():
    client_pd.Recv()
    data = client_pd.recv_data
    if data["content"]:
        msg = data["content"]
        return msg
    else:
        return 0


def get_local_time():
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))


def get_values_of_dict(para_dict, para_list):
    # return list(itemgetter(*para_list)(para_dict))
    keys = para_dict.keys()
    vals = []
    for i in para_list:
        if i in keys:
            vals.append(para_dict[i])
        else:
            vals.append('')
    return vals

####################
# functions
####################


def get_objs_new(device):
    group_id, unit_id = device[:]
    objs = []
    sql = "SELECT a.obj_id FROM object_list_current a, object_list_all b WHERE a.obj_id=b.obj_id AND a.obs_stag in ('scheduled','observable') AND b.group_id='" + \
        group_id + "' AND (b.unit_id='" + unit_id + "' OR (b.unit_id LIKE '%|%'  AND b.unit_id LIKE '%" + unit_id + \
        "%' )) AND a.mode='observation' ORDER BY a.id LIMIT 10"
    res = sql_act(sql)
    if res:
        for i in res:
            objs.append(i[0])
    return objs


def get_objs_sent(device):
    group_id, unit_id = device[:]
    objs = []
    sql = "SELECT a.obj_id FROM object_list_current a, object_running_list_current b WHERE a.obj_id=b.obj_id AND a.obs_stag='sent' AND b.group_id='" + \
        group_id + "' AND b.unit_id='" + unit_id + \
        "' AND a.mode='observation' ORDER BY a.id"
    res = sql_act(sql)
    if res:
        for i in res:
            objs.append(i[0])
    return objs


def get_obj_info(obj_id):
    infs = {}
    sql = "select column_name from information_schema.columns where table_schema='public' and table_name='object_list_all'"
    cols = sql_act(sql)
    sql = "SELECT * FROM object_list_all WHERE obj_id='" + obj_id + "'"
    infs_list = sql_act(sql)
    if cols and infs_list:
        for i in range(1, len(cols)):
            infs[cols[i][0]] = str(infs_list[0][i])
        if infs['filter'] == 'clear':
            if infs['group_id'] == "XL002":
                infs['filter'] = "Lum"
            if infs['group_id'] == "XL003":
                infs['filter'] = "R"
                print(
                    "\n\033[0;31mWARNING:\033[0m The filter of %s input Error, using filter R." % obj_id)
        if len(infs['obj_name']) > 20:
            if infs['group_id'] in ['XL002', 'XL003']:
                infs['obj_name'] = infs['obj_name'][:20]
        return infs


def check_obj_window(obj_id):
    '''
        return 0/1/-1   0 Passed; 1 in window; -1 wait for begin
    '''
    while True:
        sql = "SELECT tw_begin, tw_end FROM object_list_current WHERE obj_id='" + obj_id + "'"
        tws = sql_act(sql)
        if tws:
            begin_time, end_time = tws[0][:]
            break
        # time.sleep(1)
    time_now = datetime.datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S")
    if time_now < end_time:
        if time_now > begin_time:
            return 1
        else:
            return -1
    else:
        return 0


def check_device_free():
    device_free_list = []
    for device in devices:
        if len(objs_of_device[device]) == 0:
            device_free_list.append(device)
    return device_free_list


def send_obj(obj_id, obj_info, device):
    '''
        Highly relevant to the control system on telescope.
        Here is the simulation.
        return [send_beg_time, send_end_time]
    '''
    obj_name, objsource, observer, obs_type, obs_stra, objra, objdec, objepoch, objerror, imgtype, filter, expdur, delay, frmcnt, priority, run_name = get_values_of_dict(
        obj_info, ['obj_name', 'objsour', 'observer', 'obs_type', 'obs_stra', 'objra', 'objdec', 'objepoch', 'objerror', 'imgtype', 'filter', 'expdur', 'delay', 'frmcnt', 'priority', 'run_name'])[:]
    group_id, unit_id = device[:]
    send_beg_time = time.strftime(
        "%Y-%m-%d %H:%M:%S.s", time.localtime(time.time()))
    if group_id == 'XL001':
        op_time = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
        grid_id = '0'
        try:
            strs = obj_name.split('_')
        except:
            field_id = obj_name
        else:
            grid_id = strs[0]
            field_id = strs[1]
        # The cmd for starting the observation is should be here
        cmd_type = 'append_gwac'
        b_time = op_time
        e_time = "0000-00-00T00:00:00"
        pg_act('object_running_list_current', 'insert', [{'cmd': cmd_type, 'op_sn': obj_id, 'op_time': op_time, 'op_type': 'obs', 'obj_id': obj_id, 'obj_name': obj_name, 'observer': observer, 'objra': objra, 'objdec': objdec, 'objepoch': objepoch, 'objerror': objerror, 'group_id': group_id, 'unit_id': unit_id, 'obstype': obs_type,
               'obs_stra': obs_stra, 'grid_id': grid_id, 'field_id': field_id, 'ra': objra, 'dec': objdec, 'imgtype': imgtype, 'filter': filter, 'expdur': expdur, 'delay': delay, 'frmcnt': frmcnt, 'priority': priority, 'begin_time': b_time, 'end_time': e_time, 'run_name': run_name, 'pair_id': '0', 'mode': 'observation', 'note': ''}])
    if group_id == 'XL002' or group_id == 'XL003':
        op_time = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
        # The cmd for starting the observation is should be here
        cmd_type = 'append_object'
        grid_id = '0'
        field_id = '0'
        b_time = op_time
        e_time = "0000-00-00T00:00:00"
        pg_act('object_running_list_current', 'insert', [{'cmd': cmd_type, 'op_sn': obj_id, 'op_time': op_time, 'op_type': 'obs', 'obj_id': obj_id, 'obj_name': obj_name, 'observer': observer, 'objra': objra, 'objdec': objdec, 'objepoch': objepoch, 'objerror': objerror, 'group_id': group_id, 'unit_id': unit_id, 'obstype': obs_type,
               'obs_stra': obs_stra, 'grid_id': grid_id, 'field_id': field_id, 'ra': objra, 'dec': objdec, 'imgtype': imgtype, 'filter': filter, 'expdur': expdur, 'delay': delay, 'frmcnt': frmcnt, 'priority': priority, 'begin_time': b_time, 'end_time': e_time, 'run_name': run_name, 'pair_id': '0', 'mode': 'observation', 'note': ''}])
    send_end_time = time.strftime(
        "%Y-%m-%d %H:%M:%S.s", time.localtime(time.time()))
    return [send_beg_time, send_end_time]


def check_sending_status(obj_id, send_beg_time, send_end_time):
    '''
        The sending status is from the control system on telescope.
        In may real, it will use "obj_id", "send_beg_time", "send_end_time".
        Here is the simulation.
        return 0/1    0 no send info; 1 sent;
    '''
    res_code = 1
    return res_code


def check_observing_status(device, obj_id, obj_info):
    '''
        The observing status is from the control system on telescope.
        In may real, it will use "device", "obj_id", "obj_info".
        Here is the simulation.
        return 0/1/2    0 observing; 1 complete; 2 broken; -1 Something wrong
    '''
    expdur = obj_info['expdur']
    frmcnt = obj_info['frmcnt']
    time.sleep(int(expdur)*int(frmcnt)+3)
    res_code = 1  # You should get it from the real control system on telescope
    if res_code == 1:
        return 1
    elif res_code == 2:
        return 2
    elif res_code == -1:
        return -1
    return 0


def check_sent_obj(device, obj_id, obj_info):
    global log_file
    obj_pri = obj_info['priority']
    observing_list_maintenance('add', device, obj_id, obj_pri)
    time_beg = time.time()
    print('\n****** %s : The obj %s of %s is Observing.' %
          (get_local_time(), obj_id, device_name[device]))
    log_file.write('\n****** %s : The obj %s of %s is Observing.' %
                   (get_local_time(), obj_id, device_name[device]))
    while True:
        res_of_win = check_obj_window(obj_id)
        if not res_of_win:  # res_of_win = 0 Passed
            print('\n****** %s : The obj % s of % s is Passed.' %
                  (get_local_time(), obj_id, device_name[device]))
            log_file.write('\n****** %s : The obj %s of %s is Passed.' %
                           (get_local_time(), obj_id, device_name[device]))
            observing_list_maintenance('delete', device, obj_id, obj_pri)
            pg_act('object_running_list_current', 'update', [
                   {'end_time': datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")}, {'obj_id': obj_id}])
            client_pd.Send({"obj_id": obj_id, "obs_stag": 'pass'}, [
                'update', 'object_list_current', 'obs_stag'])
            return
        else:  # only res_of_win = 1
            res_code = check_observing_status(device, obj_id, obj_info)
            if res_code == 1:  # complete
                print('\n****** %s : The obj %s of %s is Complete.' %
                      (get_local_time(), obj_id, device_name[device]))
                log_file.write('\n****** %s : The obj %s of %s is Complete.' %
                               (get_local_time(), obj_id, device_name[device]))
                observing_list_maintenance('delete', device, obj_id, obj_pri)
                pg_act('object_running_list_current', 'update', [
                       {'end_time': datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")}, {'obj_id': obj_id}])
                client_pd.Send({"obj_id": obj_id, "obs_stag": 'complete'}, [
                    'update', 'object_list_current', 'obs_stag'])
                return
            elif res_code == 2:  # broken
                print('\n****** %s : The obj %s of %s is Broken.' %
                      (get_local_time(), obj_id, device_name[device]))
                log_file.write('\n****** %s : The obj %s of %s is Broken.' %
                               (get_local_time(), obj_id, device_name[device]))
                observing_list_maintenance('delete', device, obj_id, obj_pri)
                pg_act('object_running_list_current', 'update', [
                       {'end_time': datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")}, {'obj_id': obj_id}])
                client_pd.Send({"obj_id": obj_id, "obs_stag": 'break'}, [
                    'update', 'object_list_current', 'obs_stag'])
                return
            else:
                if res_code == 3:
                    print('\n\033[0;31mWARNING:\033[0m Something Wrong with %s of %s. Please check the system.' % (
                        obj_id, device_name[device]))
                time_delay = time.time() - time_beg
                if time_delay > 1800:
                    print("\n\033[0;31mWARNING:\033[0m The obj %s of %s has waited for %s mins. Please check the system, then break current object or not." % (
                        obj_id, device_name[device], time_delay/60))


####################
# main functions
####################
def paras_init():
    global groups, units, devices, device_name, objs_of_device, obj_pri_of_device
    device_paras = None
    # while True:
    devices_para_from_file = load_params(paras_file)['devices']
    if devices_para_from_file != device_paras:
        device_paras = devices_para_from_file
        for k in device_paras:
            unit_list = []
            for i in device_paras[k]:
                if int(device_paras[k][i]['status']) == 1:
                    if str(k) not in groups:
                        groups.append(str(k))
                    unit_list.append(str(i))
                    devices.append((str(k), str(i)))
                    device_name[(str(k), str(i))] = device_paras[k][i]['name']
                    objs_of_device[(str(k), str(i))] = []
                    obj_pri_of_device[(str(k), str(i))] = []
            units[str(k)] = unit_list
        # time.sleep(5)


def log_init():
    global log_file
    cur_date = datetime.datetime.utcnow().strftime("%Y-%m-%d")
    if not os.path.exists('obslogs'):
        os.system('mkdir obslogs')
    log_file = open("obslogs/log_%s.txt" % cur_date, "a+")
    log_file.write('\n### start ###\n')
    while True:
        cur_date = datetime.datetime.utcnow().strftime("%Y-%m-%d")
        time_h = time.strftime("%H:%M", time.localtime(time.time()))
        if time_h == '07:50':
            db_init()
            log_file.close()
            log_file = open("obslogs/log_%s.txt" % cur_date, "a+")
            log_file.write('\n### start ###\n')
            time.sleep(65)


def db_init():
    running_list_cur = 'object_running_list_current'
    date_c = datetime.datetime.utcnow().strftime("%Y-%m-%d")
    sql = 'insert into '+running_list_cur.replace('current', 'history')+' (cmd,op_sn,op_time,op_type,obj_id,obj_name,observer,objra,objdec,objepoch,objerror,group_id,unit_id,obstype,obs_stra,grid_id,field_id,ra,dec,epoch,imgtype,filter,expdur,delay,frmcnt,priority,begin_time,end_time,run_name,pair_id,note,mode) SELECT cmd,op_sn,op_time,op_type,obj_id,obj_name,observer,objra,objdec,objepoch,objerror,group_id,unit_id,obstype,obs_stra,grid_id,field_id,ra,dec,epoch,imgtype,filter,expdur,delay,frmcnt,priority,begin_time,end_time,run_name,pair_id,note,mode FROM '+running_list_cur+' WHERE op_time<'+"'" + date_c + "'"
    sql_act(sql, 0)
    sql = 'delete from '+running_list_cur+' WHERE op_time<'+"'" + date_c + "'"
    sql_act(sql, 0)


def check_sent(device):
    objs = get_objs_sent(device)
    if objs:
        print('\n###### %s : The objs %s of %s: Sent.' %
              (get_local_time(), ','.join(objs), device_name[device]))
        log_file.write('\n###### %s : The obj %s of %s: Sent.' %
                       (get_local_time(), ','.join(objs), device_name[device]))
        t_pool = []
        for obj in objs:
            obj_info = get_obj_info(obj)
            t_cn = threading.Thread(
                target=check_sent_obj, args=(device, obj, obj_info,))
            t_pool.append(t_cn)
        for t in t_pool:
            t.setDaemon(True)
            t.start()


def check_sent_main():
    t_pool = []
    for device in devices:
        t_cn = threading.Thread(target=check_sent, args=(device,))
        t_pool.append(t_cn)
    for t in t_pool:
        t.setDaemon(True)
        t.start()


def check_news(actype, device):     # actype: 0 busy mode; 1 free mode
    new_objs = get_objs_new(device)
    if new_objs:
        print('\n###### %s : Case 1 of %s' %
              (get_local_time(), device_name[device]))
        log_file.write('\n###### %s : Case 1 of %s' %
                       (get_local_time(), device_name[device]))
        for obj in new_objs:
            if not checking_list_maintenance('add', obj):
                continue
            obj_info = get_obj_info(obj)
            if not obj_info:
                continue
            res_of_win = check_obj_window(obj)
            if res_of_win == 1:
                if actype or int(obj_info['priority']) > max(obj_pri_of_device[device]):
                    print('\n###### %s : The obj %s of %s: Sending.' % (
                        get_local_time(), obj, device_name[device]))
                    log_file.write('\n###### %s : The obj %s of %s: Sending.' % (
                        get_local_time(), obj, device_name[device]))
                    send_beg_time, send_end_time = send_obj(
                        obj, obj_info, device)[:]
                    res_of_send = 0
                    for i in range(5):
                        if check_sending_status(obj, send_beg_time, send_end_time):
                            res_of_send = 1
                            break
                        else:
                            time.sleep(i)
                    if res_of_send:
                        print('\n###### %s : The obj %s of %s: Send OK.' % (
                            get_local_time(), obj, device_name[device]))
                        log_file.write('\n###### %s : The obj %s of %s: Send OK.' % (
                            get_local_time(), obj, device_name[device]))
                        checking_list_maintenance('delete', obj)
                        client_pd.Send({"obj_id": obj, "obs_stag": 'sent'}, [
                                       'update', 'object_list_current', 'obs_stag'])
                        t = threading.Thread(
                            target=check_sent_obj, args=(device, obj, obj_info,))
                        t.setDaemon(True)
                        t.start()
                    else:
                        checking_list_maintenance('delete', obj)
                        print('\n###### %s : The obj %s of %s: Send Wrong.' % (
                            get_local_time(), obj, device_name[device]))
                        log_file.write('\n###### %s : The obj %s of %s: Send Wrong.' % (
                            get_local_time(), obj, device_name[device]))
                    break
                else:
                    checking_list_maintenance('delete', obj)
                    print('\n###### %s : The obj %s of %s: Not Free.' % (
                        get_local_time(), obj, device_name[device]))
                    log_file.write('\n###### %s : The obj %s of %s: Not Free.' % (
                        get_local_time(), obj, device_name[device]))
                    break
                    # continue  # can be 'break'
            elif res_of_win == 0:
                checking_list_maintenance('delete', obj)
                print('\n###### %s : The obj %s of %s: Passed.' % (
                    get_local_time(), obj, device_name[device]))
                log_file.write('\n###### %s : The obj %s of %s: Passed.' % (
                    get_local_time(), obj, device_name[device]))
                client_pd.Send({"obj_id": obj, "obs_stag": 'pass'}, [
                    'update', 'object_list_current', 'obs_stag'])
                break
            else:  # res_of_win == -1
                checking_list_maintenance('delete', obj)
                print('\n###### %s : The obj %s of %s: Need Wait.' % (
                    get_local_time(), obj, device_name[device]))
                log_file.write('\n###### %s : The obj %s of %s: Need Wait.' % (
                    get_local_time(), obj, device_name[device]))
                time.sleep(5)
                client_og.Send()
                # print('\nSend signal')
                break
    else:
        print('\n###### %s : Case 2 of %s' %
              (get_local_time(), device_name[device]))
        log_file.write('\n###### %s : Case 2 of %s' %
                       (get_local_time(), device_name[device]))
    # print('\nProcess over')


def check_news_thread():
    time_now = time.strftime(
        "%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
    print('\n###### Get news (%s)' % time_now)
    log_file.write("\n###### Get news (%s) ######" % time_now)
    device_free_list = check_device_free()
    if device_free_list:
        for device in device_free_list:
            t_pool = []
            for device in devices:
                t_cn = threading.Thread(
                    target=check_news, args=(1, device,))
                t_pool.append(t_cn)
            for t in t_pool:
                t.setDaemon(True)
                t.start()
    else:
        for device in devices:
            t_pool = []
            for device in devices:
                t_cn = threading.Thread(
                    target=check_news, args=(0, device,))
                t_pool.append(t_cn)
            for t in t_pool:
                t.setDaemon(True)
                t.start()


def check_news_main():
    while True:
        # print('\nWait msg')
        msg = get_msg()
        # print('\nGet msg')
        if msg:
            t = threading.Thread(target=check_news_thread)
            t.setDaemon(True)
            t.start()
        # print('\nOVER')


def main():
    global db, log_file
    try:
        print('Init...')
        # db = con_db()
        paras_init()
        t = threading.Thread(target=log_init)
        t.setDaemon(True)
        t.start()
        db_init()
        print('Going...')
        check_sent_main()
        check_news_main()
    except KeyboardInterrupt:
        # if db:
        #     db.close()
        if log_file:
            log_file.close()


if __name__ == "__main__":
    main()
